use super::Diagnostics;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};

declare_tool_lint! {
    /// Warns against unsupported Rust features.
    ///
    /// For now this lints against usage of `dyn` types.
    pub creusot::EXPERIMENTAL,
    Warn,
    "using Rust features that only have basic or experimental support in Creusot"
}

pub struct Experimental {}

impl_lint_pass!(Experimental => [EXPERIMENTAL]);

fn is_dyn_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
    matches!(cx.typeck_results().expr_ty(e).peel_refs().kind(), ty::Dynamic { .. })
}

impl<'tcx> LateLintPass<'tcx> for Experimental {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx rustc_hir::Expr<'tcx>) {
        if e.span.in_external_macro(cx.sess().source_map()) {
            return;
        }
        // This is necessary because the string generated by `assert!` has no span and thus 'is not' in an external macro.
        let parent = cx.tcx.hir_parent_id_iter(e.hir_id).next().unwrap();
        if cx.tcx.hir_span(parent).in_external_macro(cx.sess().source_map()) {
            return;
        }

        if is_dyn_ty(cx, e) {
            cx.emit_span_lint(EXPERIMENTAL, e.span, Diagnostics::DynExperimental);
        }
    }
}
